Para obtener todos los activos pertenecientes al Ćndice S&P 500, en cada momento del tiempo, debemos hacer un web scraping. Tomaremos como referencia la pĆ”gina de Wikipedia.
library(rvest)
# Web-scrape SP500 stock list
sp_500 <- read_html("https://en.wikipedia.org/wiki/List_of_S%26P_500_companies") %>%
html_node("table.wikitable") %>%
html_table()
# Format names
names(sp_500) <- sp_500 %>%
names() %>%
str_to_lower() %>%
make.names()
# Show results
sp_500
A los efectos del trabajo, nos interesa conocer los cambios que hubo en el Ćndice. Para ello, generaremos un código que nos permita conocer quĆ© cambios hubo en los componentes pertencientes al Ćndice. Nos dirĆ” quĆ© activos fueron reemplazados por quĆ© otros y en quĆ© momento.
wikispx <- read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')
currentconstituents <- wikispx %>%
html_node('#constituents') %>%
html_table(header = TRUE)
currentconstituents
currentconstituents$Date <- currentconstituents$`Date first added`
spxchanges <- wikispx %>%
html_node('#changes') %>%
html_table(header = FALSE, fill = TRUE) %>%
dplyr::filter(row_number() > 2) %>% # First two rows are headers
`colnames<-`(c('Date','AddTicker','AddName','RemovedTicker','RemovedName','Reason')) %>%
mutate(Date = as.Date(Date, format = '%B %d, %Y'),
year = year(Date),
month = month(Date))
spxchanges
Antes de conformar el portafolio, es interesante deternos a analizar los distinos sectores y la frecuencia con la que aparecen:
sp_500 %>%
# Summarise data by frequency
group_by(gics.sector) %>%
summarise(count = n()) %>%
# Visualize
ggplot(aes(x = gics.sector %>% fct_reorder(count),
y = count
)) +
geom_bar(stat = "identity") +
geom_text(aes(label = count), size = 3, nudge_y = 4, nudge_x = .1) +
scale_y_continuous(limits = c(0,100)) +
ggtitle(label = "Sector Frequency Among SP500 Stocks") +
xlab(label = "GICS Sector") +
theme(plot.title = element_text(size = 16)) +
coord_flip()
Ahora, una vez que entendimos que hay once senctores, debemos pasar a elegir los activos con los que trabajaremos en el resto del ejercicio. Para ello, haremos una selección aleatoria en dos partes: por un lado, tomaremos una acción de cada sector (cumpliendo el requerimiento) y, las nueve acciones restantes serÔn elegidas de manera aleatoria. Desde ya, tendremos que asegurarnos que ninguna acción se repita.
#Selecciono a partir del sector
sample_part_1 <- sp_500 %>%
group_by(gics.sector)%>%
slice_sample(n=1)
#Selección aleatoria
sample_part_2 <- sample(sp_500$symbol, 9)
#Selección completa
full_sample <- data.table(symbol= c(sample_part_1$symbol, sample_part_2))
#Chequeo que no se repitan
if(length(unique(full_sample$symbol)) == 20){
"TRUE"
}
## [1] "TRUE"
Una vez realizada la selección de las acciones del portafolio, almacenaremos los nombres de los sĆmbolos en āfull_sample.ā
#
full_sample <- data.table(symbols= c('GOOGL', 'NKE', 'CAG', 'COP', 'SPGI', 'TMO', 'CMI',
'GLW', 'HWM', 'ESS', 'NRG', 'NDSN', 'MNST', 'VICI',
'ADP', 'KIM', 'ADBE', 'LIN', 'FITB', 'HPE'))
Con la función tq_get obtendremos los precios de las acciones desde 2010 hasta junio 2022.
prices_sp_500 <- full_sample$symbol %>%
tq_get(get = "stock.prices",
from = "2010-01-01",
to = "2022-06-30") %>%
group_by(symbol)
De la misma manera que con el Ćtem anterior, obtendremos los precios de los ETF con la función tq_get.
QUE SON LOS ETF?????
prices_etf <- c("XLK", "IVV", "VOO") %>%
tq_get(get = "stock.prices",
from = "2010-01-01",
to = "2022-03-31") %>%
group_by(symbol)
Para poder realizar la optimización del portafolio, primero debemos obtener los retornos de las acciones.
# Creamos un vector con los sĆmbolos
symbols <- full_sample$symbol
# Cargamos la data de los precios de 2010 hasta junio 2022
prices <- quantmod::getSymbols(
Symbols = symbols,
src = "yahoo",
from = "2010-1-1",
to = "2022-6-30",
auto.assign = TRUE,
warnings = FALSE
) %>%
purrr::map(.f = ~ quantmod::Ad(get(x = .x))) %>%
purrr::reduce(.f = merge) %>%
`colnames<-`(value = symbols)
Luego, obtenemos los retornos mensuales:
asset_returns_xts <- xts::to.monthly(
x = prices,
drop.time = TRUE,
indexAt = "lastof",
OHLC = FALSE
) %>%
PerformanceAnalytics::Return.calculate(method = "discrete") %>%
stats::na.omit()
Lo mostramos en la siguiente tabla:
asset_returns_xts_3[,1:10]%>%DT::datatable(extensions = 'Buttons',
options = list(dom = 'Blfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(list(className = 'dt-center', targets = 5)),
pageLength = 5, autoWidth = TRUE ))
asset_returns_xts_3[,11:20]%>%DT::datatable(extensions = 'Buttons',
options = list(dom = 'Blfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(list(className = 'dt-center', targets = 5)),
pageLength = 5, autoWidth = TRUE ))
Adicionalmente, podemos armar un grƔfico con los retornos de, por ejemplo, diez de nuestras acciones del portafolio:
Ahora, definimos los trimestres a analizar.
trimesters <- seq(as.Date("2010-01-01"), as.Date("2022-06-30"), by=90)
trimesters
## [1] "2010-01-01" "2010-04-01" "2010-06-30" "2010-09-28" "2010-12-27"
## [6] "2011-03-27" "2011-06-25" "2011-09-23" "2011-12-22" "2012-03-21"
## [11] "2012-06-19" "2012-09-17" "2012-12-16" "2013-03-16" "2013-06-14"
## [16] "2013-09-12" "2013-12-11" "2014-03-11" "2014-06-09" "2014-09-07"
## [21] "2014-12-06" "2015-03-06" "2015-06-04" "2015-09-02" "2015-12-01"
## [26] "2016-02-29" "2016-05-29" "2016-08-27" "2016-11-25" "2017-02-23"
## [31] "2017-05-24" "2017-08-22" "2017-11-20" "2018-02-18" "2018-05-19"
## [36] "2018-08-17" "2018-11-15" "2019-02-13" "2019-05-14" "2019-08-12"
## [41] "2019-11-10" "2020-02-08" "2020-05-08" "2020-08-06" "2020-11-04"
## [46] "2021-02-02" "2021-05-03" "2021-08-01" "2021-10-30" "2022-01-28"
## [51] "2022-04-28"
Optimización general:
data_p2 = asset_returns_xts
# create specification
port = portfolio.spec(assets = c(colnames(data_p2)))
# add long only constraint
port = add.constraint(portfolio = port, type = "long_only")
# add full investment contraint
port = add.constraint(portfolio = port, type = "full_investment")
# objective: manimise risk
port_rnd = add.objective(portfolio = port, type = "risk", name = "StdDev")
# objective: maximise return
port_rnd = add.objective(portfolio = port_rnd, type = "return", name = "mean")
# 1. optimise random portfolios
rand_p = optimize.portfolio(R = data_p2, portfolio = port_rnd, optimize_method = "random",
trace = TRUE, search_size = 100)
port_msd = add.objective(portfolio = port, type = "risk", name = "StdDev")
minvar1 = optimize.portfolio(R = data_p2, portfolio = port_msd, optimize_method = "ROI")
minvar1
## ***********************************
## PortfolioAnalytics Optimization
## ***********************************
##
## Call:
## optimize.portfolio(R = data_p2, portfolio = port_msd, optimize_method = "ROI")
##
## Optimal Weights:
## GOOGL NKE CAG COP SPGI TMO CMI GLW HWM ESS NRG
## 0.0000 0.0439 0.1448 0.0000 0.0000 0.1769 0.0000 0.0785 0.0000 0.2240 0.0119
## NDSN MNST VICI ADP KIM ADBE LIN FITB HPE
## 0.0342 0.0464 0.0000 0.1229 0.0000 0.0595 0.0569 0.0000 0.0000
##
## Objective Measure:
## StdDev
## 0.04617
Graficamos:
tailoredFrontierPlot(eff_front2, sharpeRatio = FALSE, risk = "Sigma")